package com.tyk.bookstore.back.product.service.impl;


import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.tyk.bookstore.back.comment.service.CommentApiService;
import com.tyk.bookstore.back.common.core.model.constant.OssConstant;
import com.tyk.bookstore.back.common.core.model.result.CommonResultEnum;
import com.tyk.bookstore.back.common.core.model.type.BinVo;
import com.tyk.bookstore.back.common.core.model.type.SuggestionQuery;
import com.tyk.bookstore.back.common.core.util.AssertUtil;
import com.tyk.bookstore.back.common.core.util.FileUtil;
import com.tyk.bookstore.back.common.core.util.IdGenerator;
import com.tyk.bookstore.back.common.core.util.ObjUtil;
import com.tyk.bookstore.back.common.mq.RabbitManager;
import com.tyk.bookstore.back.common.dal.mapper.dto.BinDto;
import com.tyk.bookstore.back.common.other.oss.manager.OssManager;
import com.tyk.bookstore.back.product.manager.excel.model.BookExcelVo;
import com.tyk.bookstore.back.product.manager.prodcut.ProductManager;
import com.tyk.bookstore.back.product.model.entity.*;
import com.tyk.bookstore.back.product.model.enume.BookState;
import com.tyk.bookstore.back.product.model.query.BookAddQuery;
import com.tyk.bookstore.back.product.model.query.BookPageQuery;
import com.tyk.bookstore.back.product.model.query.BookUpdateQuery;
import com.tyk.bookstore.back.product.model.result.AuthorResultEnum;
import com.tyk.bookstore.back.product.model.result.BookResultEnum;
import com.tyk.bookstore.back.product.model.result.ClazzResultEnum;
import com.tyk.bookstore.back.product.model.result.PublisherResultEnum;
import com.tyk.bookstore.back.product.model.vo.BookPageVo;
import com.tyk.bookstore.back.product.model.vo.BookSuggestionVo;
import com.tyk.bookstore.back.product.model.vo.BookVo;
import com.tyk.bookstore.back.product.mq.BookMqConstant;
import com.tyk.bookstore.back.product.repository.*;
import com.tyk.bookstore.back.product.service.BookService;
import com.tyk.bookstore.back.product.util.BookUtil;
import com.tyk.bookstore.back.product.util.MapStructProduct;
import com.tyk.bookstore.back.storage.service.StockApiService;
import com.tyk.bookstore.front.common.cache.anno.ICacheRemove;
import com.tyk.bookstore.front.common.cache.constant.CacheConstant;
import lombok.RequiredArgsConstructor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.web.multipart.MultipartFile;

import java.math.BigInteger;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @author tyk
 * @description 书籍管理
 */
@Service
@RequiredArgsConstructor
public class BookServiceImp implements BookService {
    private final BookRepository bookRepository;
    private final BookStatisticRepository bookStatisticRepository;
    private final BookCollectRepository bookCollectRepository;
    private final BookPraiseRepository bookPraiseRepository;
    private final AuthorRepository authorRepository;
    private final PublisherRepository publisherRepository;
    private final DisplayImageRepository displayImageRepository;
    private final TransactionTemplate transactionTemplate;
    private final OssManager ossManager;
    private final MapStructProduct mapStructProduct;
    private final ProductManager productManager;
    private final SqlSessionFactory sqlSessionFactory;
    private final RabbitManager rabbitManager;
    private final ClazzRepository clazzRepository;
    private final StockApiService stockApiService;
    private final CommentApiService commentApiService;

    @Override
    public BookVo getById(Long id) {

        // 判断书籍是否存在
        AssertUtil.isTrue(
                bookRepository.exist1Eq(BookDo::getId, id, BookDo.class),
                BookResultEnum.BOOK_NOT_FOUND
        );

        // 书籍信息、书籍统计信息
        BookDo bd = bookRepository.selectById(id);
        BookVo bv = mapStructProduct.toBookVo(
                bookRepository.selectById(id),
                bookStatisticRepository.selectById(id)
        );

        // 作者
        bv.setAuthorName(
                authorRepository.<String>selectSin1Eq(
                        AuthorDo::getName, AuthorDo::getId, bd.getAuthorId(), AuthorDo.class
                ).getV1()
        );

        // 出版社
        bv.setPublisherName(
                publisherRepository.<String>selectSin1Eq(
                        PublisherDo::getName, PublisherDo::getId, bd.getPublisherId(), PublisherDo.class
                ).getV1()
        );

        // 各级分类
        bv.setClazzList(
                mapStructProduct.toBookVo$ClazzNameVoList(
                        productManager.buildClazzNameVoList(bd.getClazzId())
                )
        );

        // 展示图片
        List<String> imgList = displayImageRepository
                .<String>selectSinList1Eq(
                        DisplayImageDo::getPath,
                        DisplayImageDo::getBookId, bd.getId(),
                        DisplayImageDo.class
                )
                .stream().map(it -> FileUtil.fullFilePath(it.getV1()))
                .toList();
        bv.setImgList(imgList);

        // 返回
        return bv;
    }

    @Override
    public BookPageVo getPage(BookPageQuery query) {
        // 构建查询条件
        Page<BookDo> page = new Page<>(query.getPage(), query.getLimit());
        LambdaQueryWrapper<BookDo> w = buildQueryWrapper(query);
        bookRepository.selectPage(page, w);
        // 转换
        BookPageVo bookPageVo = mapStructProduct.toBookPageVo(page);
        // 补充信息
        bookPageVo.getRecords().forEach(book -> {
            book.setClazzFullName(productManager.buildClazzFullName(book.getClazzId()));
        });
        // 返回书籍列表
        return bookPageVo;
    }

    @Override
    public void add(BookAddQuery query) {

        // 关联参数检查
        if (ObjUtil.equal(query.getState(), BookState.ON)) {
            AssertUtil.notNull(query.getTimedRelease(), CommonResultEnum.PARAM_ERROR);
            if (query.getTimedRelease())
                AssertUtil.notNull(query.getReleaseTime(), CommonResultEnum.PARAM_ERROR);
        }

        query.setWeight(0L);

        // 检查是否已存在
        AssertUtil.isFalse(
                bookRepository.exist1Eq(BookDo::getIsbn, query.getIsbn(), BookDo.class),
                BookResultEnum.BOOK_ALREADY_EXISTS
        );

        // 作者存在
        AssertUtil.isTrue(
                authorRepository.exist1Eq(AuthorDo::getId, query.getAuthorId(), AuthorDo.class),
                AuthorResultEnum.AUTHOR_NOT_FOUND
        );

        // 出版社存在
        AssertUtil.isTrue(
                publisherRepository.exist1Eq(PublisherDo::getId, query.getPublisherId(), PublisherDo.class),
                PublisherResultEnum.PUBLISHER_NOT_FOUND
        );

        // 分类存在
        AssertUtil.isTrue(
                clazzRepository.exist1Eq(ClazzDo::getId, query.getClazzId(), ClazzDo.class),
                ClazzResultEnum.CLAZZ_NOT_FOUND
        );
        AssertUtil.isFalse(
                clazzRepository.exist1Eq(ClazzDo::getParentId, query.getClazzId(), ClazzDo.class),
                ClazzResultEnum.CLAZZ_HAS_CHILD
        );

        // 转DO
        BookDo book = mapStructProduct.toBookDo(query);

        // 分配书籍编号
        String bookNo = BookUtil.bookNo(bno -> bookRepository.exist1Eq(BookDo::getBookNo, bno, BookDo.class));
        AssertUtil.notBlank(bookNo, CommonResultEnum.NETWORK_ERROR);
        book.setBookNo(bookNo);

        // 查看是否定时上架
        boolean timedRelease = ObjUtil.equal(query.getState(), BookState.ON) && ObjUtil.equal(query.getTimedRelease(), true);
        if (timedRelease) book.setState(BookState.OFF);

        // 书籍ID
        Long bookId = IdGenerator.id();
        book.setId(bookId);

        // 添加书籍、书籍统计信息、书籍展示图片
        transactionTemplate.executeWithoutResult(status -> {
            bookRepository.insert(book);
            bookStatisticRepository.insert(new BookStatisticDo(bookId, query.getPrice()));
            displayImageRepository.insertBatch(
                    query.getImgList().stream().map(it -> new DisplayImageDo(bookId, it)).toList(),
                    sqlSessionFactory
            );
        });


        // 查看是否定时上架
        if (timedRelease) {
            rabbitManager.sendDelayMsg(
                    BookMqConstant.BOOK_RELEASE_EXCHANGE,
                    BookMqConstant.BOOK_RELEASE_ROUTING,
                    book.getId(),
                    Duration.between(LocalDateTime.now(), query.getReleaseTime()).getSeconds()
            );
        }

    }

    @ICacheRemove(prefix = CacheConstant.PRODUCT_DETAIL_PREFIX)
    @Override
    public void updateById(BookUpdateQuery query) {

        // 关联参数检查
        if (ObjUtil.equal(query.getState(), BookState.ON)) {
            AssertUtil.notNull(query.getTimedRelease(), CommonResultEnum.PARAM_ERROR);
            if (query.getTimedRelease())
                AssertUtil.notNull(query.getReleaseTime(), CommonResultEnum.PARAM_ERROR);
        }
        // 判断书籍是否存在
        AssertUtil.isTrue(
                bookRepository.exist1Eq(BookDo::getId, query.getId(), BookDo.class),
                BookResultEnum.BOOK_NOT_FOUND
        );

        // 校验ISBN是否重复
        AssertUtil.isFalse(
                bookRepository.exist1Eq1Ne(
                        BookDo::getIsbn, query.getIsbn(),
                        BookDo::getId, query.getId(),
                        BookDo.class),
                BookResultEnum.BOOK_ALREADY_EXISTS
        );

        // 转换
        BookDo book = mapStructProduct.toBookDo(query);

        // 查看是否定时上架
        boolean timedRelease = ObjUtil.equal(query.getState(), BookState.ON) && ObjUtil.equal(query.getTimedRelease(), true);
        if (timedRelease) {
            book.setState(BookState.OFF);
            rabbitManager.sendDelayMsg(
                    BookMqConstant.BOOK_RELEASE_EXCHANGE,
                    BookMqConstant.BOOK_RELEASE_ROUTING,
                    book.getId(),
                    Duration.between(LocalDateTime.now(), query.getReleaseTime()).getSeconds()
            );
        }

        // 更新展示图片、统计数据、书籍
        transactionTemplate.executeWithoutResult(status -> {
            if (ObjUtil.notEmpty(query.getImgList()))
                displayImageRepository.insertBatch(
                        query.getImgList().stream().map(path -> new DisplayImageDo(book.getId(), path)).toList(),
                        sqlSessionFactory
                );
            bookStatisticRepository.update1Set1Eq(
                    BookStatisticDo::getPrice, book.getPrice(),
                    BookStatisticDo::getBookId, book.getId(),
                    BookStatisticDo.class
            );
            bookRepository.updateById(book);

        });

    }

    @ICacheRemove(prefix = CacheConstant.PRODUCT_DETAIL_PREFIX)
    @Override
    public void deleteById(Long id) {
        // 书籍存在
        AssertUtil.isTrue(
                bookRepository.exist1Eq(BookDo::getId, id, BookDo.class),
                BookResultEnum.BOOK_NOT_FOUND
        );
        // 书籍无库存
        AssertUtil.isFalse(
                stockApiService.hasStock(id),
                BookResultEnum.BOOK_HAS_STOCK
        );
        // 删除相关信息
        transactionTemplate.executeWithoutResult(status -> {
            // 删除展示图片
            displayImageRepository.delete1Eq(
                    DisplayImageDo::getBookId, id, DisplayImageDo.class
            );
            // 删除统计信息
            bookStatisticRepository.delete1Eq(
                    BookStatisticDo::getBookId, id, BookStatisticDo.class
            );
            // 删除收藏信息
            bookCollectRepository.delete1Eq(
                    BookCollectDo::getBookId, id, BookCollectDo.class
            );
            // 删除点赞信息
            bookPraiseRepository.delete1Eq(
                    BookPraiseDo::getBookId, id, BookPraiseDo.class
            );
            // 删除书籍
            bookRepository.deleteById(id);
            // 删除评论信息
            commentApiService.deleteByBookId(id);
        });
    }

    @Override
    public void deleteBatchByIdSet(Set<Long> idSet) {
        AssertUtil.equal(
                bookRepository.count1In(
                        BookDo::getId, idSet, BookDo.class
                ),
                (long) idSet.size(),
                BookResultEnum.BOOK_NOT_FOUND
        );
        transactionTemplate.executeWithoutResult(status -> {
            displayImageRepository.delete1In(
                    DisplayImageDo::getBookId, idSet, DisplayImageDo.class
            );
            bookStatisticRepository.delete1Eq(
                    BookStatisticDo::getBookId, idSet, BookStatisticDo.class
            );
            bookRepository.deleteBatchIds(idSet);
        });
    }

    @Override
    public void changeBookStatus(Long id, BookState state) {

        // 判断书籍是否存在
        AssertUtil.isTrue(
                bookRepository.exist1Eq(BookDo::getId, id, BookDo.class),
                BookResultEnum.BOOK_NOT_FOUND
        );

        // 书籍存在
        AssertUtil.isTrue(
                bookRepository.exist1Eq(
                        BookDo::getId, id, BookDo.class
                ),
                BookResultEnum.BOOK_NOT_FOUND
        );

        // 修改书籍状态
        bookRepository.update1Set1Eq(
                BookDo::getState, state,
                BookDo::getId, id,
                BookDo.class
        );

        // 书籍下架则清除收藏信息
        if (BookState.OFF.equals(state)) {
            bookCollectRepository.delete1Eq(
                    BookCollectDo::getBookId, id, BookCollectDo.class
            );
        }

    }

    @Override
    public String uploadCover(MultipartFile file) {
        return ossManager.upload(file, OssConstant.BOOK_COVER_PREFIX);
    }

    @Override
    public List<String> addDisplayImageList(List<MultipartFile> fileList) {
        return fileList.stream().map(file -> ossManager.upload(file, OssConstant.BOOK_DISPLAY_IMG_PREFIX)).toList();
    }

    @Override
    public String addDisplayImage(MultipartFile file) {
        return ossManager.upload(file, OssConstant.BOOK_DISPLAY_IMG_PREFIX);
    }


    @Override
    public void deleteDisplayImageList(Set<String> urlSet) {
        urlSet = urlSet.stream().map(FileUtil::relativePath).collect(Collectors.toSet());
        displayImageRepository.delete1In(
                DisplayImageDo::getPath, urlSet,
                DisplayImageDo.class
        );
    }


    @Override
    public List<BookSuggestionVo> getSuggestionList(SuggestionQuery query) {
        return mapStructProduct.toBookSuggestionVoList(
                bookRepository.selectBookSuggestionDtoList(
                        query.getKeyword()
                )
        );
    }

    @Override
    public List<BookExcelVo> getBookExcelVoList(BookPageQuery query) {

        LambdaQueryWrapper<BookDo> w = buildQueryWrapper(query);
        List<BookDo> doList = bookRepository.selectList(w);

        // 作者
        Map<Long, String> aMap = authorRepository.<BigInteger, String, Long>selectBinList1In(
                AuthorDo::getId, AuthorDo::getName,
                AuthorDo::getId, doList.stream().map(BookDo::getAuthorId).collect(Collectors.toSet()),
                AuthorDo.class
        ).stream().collect(Collectors.toMap(
                it -> it.getV1().longValue(),
                BinDto::getV2
        ));

        // 出版社
        Map<Long, String> pMap = publisherRepository.<BigInteger, String, Long>selectBinList1In(
                PublisherDo::getId, PublisherDo::getName,
                PublisherDo::getId, doList.stream().map(BookDo::getPublisherId).collect(Collectors.toSet()),
                PublisherDo.class
        ).stream().collect(Collectors.toMap(
                it -> it.getV1().longValue(),
                BinDto::getV2
        ));

        // 分类
        Map<Long, String> cMap = productManager.buildClazzFullName(
                doList.stream().map(BookDo::getClazzId).collect(Collectors.toSet())
        ).stream().collect(Collectors.toMap(
                BinVo::getId,
                BinVo::getValue
        ));

        // 统计数据
        List<Long> bidList = doList.stream().map(BookDo::getId).toList();
        Map<Long, BookStatisticDo> bsMap = bookStatisticRepository.selectList(
                Wrappers.lambdaQuery(BookStatisticDo.class).in(BookStatisticDo::getBookId, bidList)
        ).stream().collect(Collectors.toMap(
                BookStatisticDo::getBookId,
                it -> it
        ));

        // 转换
        return doList.stream().map(it -> {
            BookStatisticDo bs = bsMap.get(it.getId());
            BookExcelVo vo = mapStructProduct.toBookExcelVo(it, bs);
            vo.setAuthorName(
                    aMap.get(it.getAuthorId())
            );
            vo.setPublisherName(
                    pMap.get(it.getPublisherId())
            );
            vo.setClazzFullName(
                    cMap.get(it.getClazzId())
            );
            return vo;
        }).toList();
    }

    @Override
    public void saveBookExcelVoList(List<BookExcelVo> bookList) {
        List<BookDo> doList = mapStructProduct.toBookDoList(bookList);
        doList.forEach(it -> {
            // 分配书籍编号
            String bookNo = BookUtil.bookNo(bno -> bookRepository.exist1Eq(BookDo::getBookNo, bno, BookDo.class));
            AssertUtil.notBlank(bookNo, CommonResultEnum.NETWORK_ERROR);
            it.setBookNo(bookNo);
            // 状态为下架
            it.setState(BookState.OFF);
            //  ID
            it.setId(IdGenerator.id());
        });
        // 添加
        bookRepository.insertBatch(doList);

    }


    /**
     * 构建书籍查询条件
     */
    private LambdaQueryWrapper<BookDo> buildQueryWrapper(BookPageQuery query) {

        LambdaQueryWrapper<BookDo> w = new LambdaQueryWrapper<>();
        if (query == null) return w;

        w.eq(query.getId() != null, BookDo::getId, query.getId());

        w.eq(ObjUtil.notBlank(query.getBookNo()), BookDo::getBookNo, query.getBookNo());

        w.eq(ObjUtil.notBlank(query.getIsbn()), BookDo::getIsbn, query.getIsbn());

        w.eq(query.getState() != null, BookDo::getState, query.getState());

        w.like(ObjUtil.notBlank(query.getNameLike()), BookDo::getName, query.getNameLike());

        w.in(ObjUtil.notEmpty(query.getAuthorIdList()), BookDo::getAuthorId, query.getAuthorIdList());

        w.ge(query.getPriceFrom() != null, BookDo::getPrice, query.getPriceFrom());

        w.le(query.getPriceTo() != null, BookDo::getPrice, query.getPriceTo());
        if (query.getClazzId() != null) {
            List<Long> clazzIdList = productManager.findClazzLeafIdList(query.getClazzId());
            w.in(BookDo::getClazzId, clazzIdList);
        }

        w.eq(query.getPublisherId() != null, BookDo::getPublisherId, query.getPublisherId());

        w.ge(query.getPublishTimeFrom() != null, BookDo::getPublishTime, query.getPublishTimeFrom());
        w.le(query.getPublishTimeTo() != null, BookDo::getPublishTime, query.getPublishTimeTo());

        w.ge(query.getStartTime() != null, BookDo::getCreateTime, query.getStartTime());
        w.le(query.getEndTime() != null, BookDo::getCreateTime, query.getEndTime());

        w.orderBy(true, false, BookDo::getCreateTime);

        return w;
    }

}
